查看原文
其他

腾讯互娱架构师谈游戏服务器缓存系统怎么造(有彩蛋)

2017-01-04 韩伟 DBAplus社群



本文根据DBAplus社群第88期线上分享整理而成,文末还有书送哦~


讲师介绍


韩伟

腾讯科技互娱研发部架构师


  • 曾在网易任职8年,担任无线事业部产品总监。

  • 多年来一直从事技术开发,擅长开发高性能系统,对于软件架构设计也有丰富的经验。

  • 个人的技术兴趣在设计模式、软件体系架构等提高软件开发效率方面的知识。


主题简介:

1、游戏类业务的通信模型分析

2、游戏类业务的数据处理流程分析

3、‍一般缓存系统的特点在游戏中的问题

4、现代游戏服务器端的几个典型运行时架构


一、背景


在中国的互联网诸多业务领域中,游戏一直是充当“现金牛”而存在的。常言道“隔行如隔山”,游戏领域和互联网其他领域确实可以说是不同的两个行业。但是,在游戏服务器端开发领域中的很多重要问题,并没有被明确的分辨出其特异性,从而得到专门的对待。


我们不管是在业界开源领域,还是内部分享中,很少会有专门针对游戏业务特征进行专门设计的组件、类库或者框架。我们从游戏的客户端方面来看,一款专业的游戏客户端引擎,已经是游戏开发的标配,比如最早的Flash Builder,到后期的Cocos2d-X,Unity,Unreal;但是服务器端,我们几乎找不到同样重量级的产品。

 

在游戏服务器端开发所有要面对的问题中,有两个是最核心和最普遍的:一是和客户端的通讯;二是游戏登录用户的数据处理。对于和客户端通讯的这个问题,大量的游戏开发者会使用“通用”的开源组件,比如Protocol Buffer、Thrift、Jetty、Node.js等等通信或RPC框架。虽然针对游戏,还是要做大量的改造,但一般都有很多现成的代码可供修改。

 

在一般的互联网应用中,我们一般认为服务都是通过请求-响应的方式来完成的。而在游戏业务领域中,请求-响应可以看成是一种类型的通讯方式,但还有另外一种重要的通讯模型,就是“数据同步”方式:游戏中某个角色的HP、位置坐标改变了,需要在客户端和服务器之间、客户端和客户端之间同步。这造成了一般情况下通信协议的大量增加。

 

对于第二个问题,不管是Memcache还是MySQL,或者是Redis,都不能完全满足游戏开发者的需求。很多团队尝试过各种组合和修改,试图创造出利用现有开源软件,建设既能迎合灵活的需求变化,又具备高延迟和高可用的数据处理系统,但最后这些努力基本上都很难圆满成功。因为我们在游戏服务器端代码中,还是充斥着大量的内存、缓存管理,数据同步、落地等等代码。而且每个游戏都要重新去写一遍这些类似的功能,不能不说是一种浪费。

 

如果我们要想出一种能满足“游戏”这个业务领域的数据系统设计,那么就一定要搞清楚为什么在如此之多的开源项目和游戏团队中,没能实现完美契合的原因。


二、电子商务/一般互联网业务的C-S通讯流程


基于Web Service类型的通讯模型,现在基本已经成为互联网开源组件的标准。由此而诞生的RESTful API,或者各种RPC模型,其实都是基于这样的客观事实:




用户主动请求,服务器产生回应。典型的就是网页的点击、表单的提交。


主动通知的消息,仅仅是提示用户发起查询请求。比如在APP按钮上的小红点,消息页的数字提示等等,这些主动通知都是为了通知用户去刷新页面。




三、游戏类业务的通信模型分析


游戏中的通信,一般和操作有关。这些操作一般分为两类:

  • UI面板类操作

  • 战斗场景操作


这两者的最大区别,就是UI面板类操作一般无需让其他玩家看见。而战斗场景操作则需要广播给所有玩家看到。




在第二种情况下,一般就不是客户端主动发起,而是服务器端直接推送实际数据,然后客户端直接显示这些数据。这个模式和简单的“推送”还不一样,而应该更进一步,是一种从服务器端发起的,向客户端“同步”数据的请求。


因此,一个好的游戏服务器端框架,应该是能同时支持请求-响应模型和“推送同步”模型的。


四、电子商务/一般互联网类业务的数据处理流程


Memcache、Redis、MySQL在一般互联网业务中的应用非常广泛。而且基本上能很好的应对各种常见的应用场景,包括类似BBS的社区、新闻门户、电子商务类系统。


在企业内部信息系统中(Intranet),这一类数据软件也能发挥非常好的功效。由于电子商务类是其中最复杂的系统,所以我在这里以此为例说明,一般数据处理的流程是如何的。




假设我们浏览了一个网店,选中了一个商品,点击了下单这个流程,实际上需要的后台流程可能是下图所示:



从上面的分析大概可以总结出几个特点:


1、忍受延迟:每个操作的延迟要求较低,操作频率不会太高。一般我们页面在5秒内打开,都不会引起太多客户的抗议。所以,就算我们处理一个请求的时候,后台进行多次的进程间调用,产生的延迟和带宽消耗也是可以忍受的。


2、在线交互少:互联网业务大多数是基于浏览器的,所以在线用户之间很少实时交互。


3、数据分散:一般来说,互联网应用的数据可以在多个不同的业务系统中共用,但是需要专门的业务模块来做管理,以维持数据的一致性。

4、数据变更面广:系统需要持续处理很多数据变更,互联网业务有很大一部分数据是来源于普通用户、网络编辑、店主等等使用者,在使用的过程中,他们会大量的修改系统所存储的数据。

 

以上四个特点,导致了我们一般会把后台要处理的数据,分别用Cache系统和DB系统来处理。并且,我们一般会按业务功能划分模块,同时也划分业务系统。由于延迟和在线交互的需求较弱,所以使用大量进程来做模块隔离,依然是非常可行的,总体来说,就是一种比较“分散”的数据使用方式。


五、游戏类业务的数据处理流程分析


在各种游戏中,MMORPG是数据处理最为复杂的一类,也是最典型的一种“重服务器端”的游戏类型,因此可以作为游戏业务中通用性的参考标准。在MMORPG中,我们可以发现,数据的处理需求,和一般互联网业务大相径庭,它体现出的是一种明显的“集中”式的数据处理需求。我们可以从一般MMORPG的服务器架构中体现出来:




在游戏业务中,一般我们都会发现以下的特点:


1、延迟敏感:游戏中用户会产生大量操作,都要求“实时”进行反馈,所以一般都不能忍受1秒以上的延迟,在大量动作类型的游戏中,一般都会要求服务器的反馈时延在50ms左右。因此游戏开发者都习惯于尽量减少后台进程间的交互,尽管这对提高系统吞吐量很不利。所以大部分游戏服务器端都有一个所谓“GameServ-er”,里面运行了游戏70%以上的功能。


2、大量实时交互在线游戏的特点,就是很多玩家可以通过服务器“看见”彼此,能实时的互动。因此我们必须要把用户的在线数据,集中到一起,才能提供互相操作的可能;而且A用户操作B用户的数据,是最常见的数据操作,所谓战斗玩法,就是互相修改对方的数据的过程。


3数据集中游戏是一个几乎完全虚拟的世界,在游戏中的数据,实际上很少能在其他系统中产生价值。而游戏逻辑也禁止通过游戏以外的方式,修改游戏的数据。所以游戏中的数据,一般都会集中存放在单独的数据库中。由于没有数据共用的需求,所以也不需要把GameServer里面集中的逻辑划分出很多单独的进程模块来。


4、数据变更少实际上游戏的数据变更还是很快的,比如游戏中的每次中弹,都要减少HP的数值。但是游戏里的数据,一般都遵守这样一个规则:“变化越快的数据,重要性越低”。也就是说,游戏中是可以容忍一定程度的数据不一致和不完整的。而游戏中的数据,一般会分成两类:玩家存档和游戏设置。


对于玩家存档来说,其单条数据量一般不大,但会有大量的记录数,因为每个玩家都会有一个存档。但是其读取、修改,一般很典型的和玩家的登录、登出、升级等业务逻辑密切关联,所以其缓存时机是比较容易根据业务逻辑来把握的。而对于游戏设置数据来说,几乎只有升级游戏版本的时候才会修改,大部分运行时是只读的,其缓存简单的读入内存就解决问题了。


六、一般缓存系统的特点在游戏中的问题


根据以上的分析,我们可以看到,普通的缓存系统,如Memcache和Redis,实际上其特点是不太适合游戏业务的:


一般跨进程的缓存系统,无法解决游戏要求的低延迟问题。级别是同机房,每次数据存取都需要10-20ms的时间,对于游戏战斗中大量的数据读、写来说,是很难接受的。(但是一些回合制战斗、低频操作还是有用的)


通用型的缓存系统或者数据库,一般都比较难集结多个进程,形成一个完整的数据存储网格。这让玩家间的互相交互产生了额外的难度,开发者必须先想办法确定玩家的数据在哪个后台进程上,然后才能去读写。一般的数据库或缓存系统,为了保证数据的一致性或者完整性,往往会需要牺牲一些分布式的能力。而这种牺牲在游戏业务中,其实是一种浪费,因为游戏的很多数据都无需这种能力。


通用性数据系统一般不依赖于特定的语言,所以很少能直接把某种“对象”存入到数据系统中。在游戏开发中,需要存储的数据结构数量往往是非常大量的:一个普通的游戏,基本上都会超过100种数据结构。对于每个数据结构,都去建表或者编写序列化/反序列化配置,是一种非常累人的工作。——明明在代码中,已经用编程语言定义了他们的结构,还要重复的搞一次。


根据上面说的这些问题,我们实际上是需要另外一种完全不同设计思想的数据系统。对于游戏业务来说,一个好用的数据系统,应该包括这样一些特点:


可以利用GameServer进程内的内存进行自动化的缓存管理。由于GameServer进程往往集中了大部分的逻辑运算,所以大部分的数据缓存也应该在这个进程中,这样才能符合游戏所需的延迟要求。


自动进行数据落地和容灾管理。由于游戏数据中有大量的“过程数据”,所以其一致性和完整性要求会稍微低于其他业务,所以应该利用这一点,让GameServer本身也可以是分布式的程序,从而提高系统整体的吞吐量。


具备良好的编程易用性。最好是能直接存取编程中的对象,避免反复对数据结构的描述,节省大量的开发时间。


七、现代游戏服务器端的几个典型运行时架构


游戏本身的逻辑复杂性,导致了架构上也是分成很多不同的“门派”。和互联网/电商日渐趋同的架构不一样,游戏的“运行时架构”,往往会向着不同方向更加的分化,而不是统一。下面就讲讲游戏领域架构的几个主要分支:


1、MMORPG


这一类游戏主要采用“分区分服”类的架构。典型例子有《石器时代》《传奇》。从表面上看,这类游戏的服务器架构似乎非常简单,就是硬生生的把游戏世界按照硬件集群分开来,克隆出很多的个平行的游戏世界。服务器中的通讯、计算、存储能力都是每个游戏世界单独一份。但事实上,并没有这么简单。MMORPG类的游戏,在服务器端主要有几个挑战:


一个是海量网络广播的挑战,由于有大量的玩家的实时互动,所以需要广播大量的数据包;


第二个是大量计算任务需要快速的数据缓存的挑战,在游戏的战斗中,每一个动作都几乎需要对数据做读写,由于涉及大量不确定的玩家数据,所以在整个服务器中快速查询、修改玩家数据变的延迟变得非常苛刻。


但是MMORPG类游戏,在业务领域上又有几个突出的特点:


一是基本都是对在线玩家数据的操作,很少像电商那样,都是对持久化数据做操作;


二是在线数据的分布,有一个虚拟的“游戏地图”作为分布的脉络,玩家总是从一个游戏场景,走向另外一个场景,数据是按场景来聚合的;


三是游戏逻辑虽然复杂,但较少产生关联性的查询,只要少量的如“拍卖行”,“排行”这样的数据,是需要比较复杂的关联到其他数据单元的。


所以MMORPG的服务器端架构,很自然的就采用以内存作为整个虚拟世界的缓存,然后按游戏地图进行进程分布的样子。由于数据都在内存中,才能满足战斗的低延迟响应,如果好像电商类,每一个操作要几百毫秒的话,游戏简直就没法玩了。而按地图分布的进程,可以让玩家在游戏的过程中,在切换地图的过程中,把内存数据在进程间搬迁,这样既自然又实用,因为大部分的数据关联操作(比如战斗)都是以游戏地图为纽带的。


在持久化存储上,基本上都很容易使用NoSQL来做,因为几乎都是在玩家登录的时候加载数据到内存,离线登出时回写到持久化且销毁内存数据,所以完全可以只通过一个索引来完成玩家数据的存取。早期很多网络游戏甚至直接使用文件系统简单的进行玩家存档的管理。



上图非常简单的描述了MMORPG游戏服务器的基本架构,但是这只是核心逻辑的部分,实际上还有很多其他的服务器进程并没有画出来,比如聊天服务器、目录服务器、各种协调的proxy和dir服务器。那些服务器一般都以数据缓存的特点存在,如果是需要全局的,比如“拍卖行”服务器,就会让所有数据集中在一个进程上(也可能按拍卖的物品种类分布,外加搜索索引服务器进程)。


2、棋牌类


和MMORPG不同,棋牌类服务器的特点是,需要海量的用户在一起玩,比如需要有一个容纳所有玩家的游戏大厅,可以在那里找到所有的在线玩家一起玩;但是每个具体的战斗过程,又是小规模几个玩家交互的,比如需要同时开启成千上万的房间(或者桌子)来进行具体的游戏。因此这类游戏的服务器挑战,就有两个:一是全局所有玩家需要互相交互的需求;二是管理大量的小群体玩家动态交互通信群组的需求。

 

为了解决这两个问题,一般来说服务器端会分为两个部分,一是大厅服务器,一是房间服务器。大厅服务器是一个巨大的广播集群,负责不太实时的数据传输和查询。房间服务器是一组可以快速租用、退还的小型实时广播服务进程。


在大厅服务器中,所有的在在线的玩家,都按其ID来分布在多个进程中的一个,在玩家之间的查询、广播操作时,采用多个服务器并行操作,最后汇总结果的方式来提供。这样的操作延迟是会比较高,但是能让海量的用户数据存储到不同的机器上。


而房间服务器则会负责提供具体的游戏广播功能,一旦玩家组成了群组进入,大厅服务器会拷贝数据到房间服务器,而房间服务器就只对这几个玩家负责了,游戏结束则清理掉这些玩家数据,准备新的游戏。



3、MOBA类


从本质上来说,类似《英雄联盟》这类游戏,和棋牌类游戏会比较像,只不过他们的游戏逻辑比较复杂,所以“房间服务器”里面要运行的逻辑会比较多,不似棋牌类那么简单(甚至可以通用,只要有广播功能即可)。在MOBA类游戏里,有一个最大的难点,是“自动匹配”,这个功能在棋牌的大厅服务器里是比较少见的。


只有尽量在更大的范围内匹配在线玩家,等待的时间才会更短,匹配结果才更准确;然而大量的数据充斥在有限的进程空间中,本身又会导致承载压力,被迫要把数据分散到其他进程去。这个矛盾是贯穿匹配系统的问题。

 

为了解决这个问题,有些游戏采用退缩的策略,就是降低匹配的准确度,尽快的把玩家匹配起来。这样随机的把玩家放入不同进程的匹配队列中,只要人满了就开始玩,这样也是可以的。有一些游戏则费比较大的功夫,做一个分布式的内存缓存,希望尽量多的玩家在一起匹配,付出的代价就是需要更多服务器间的数据交互,以及延迟。



上面说的这集中典型的模式,在现在的游戏服务器架构中,往往并不是单一出现的。比如现在的《魔兽世界》,就可以让存档在不同服务器的玩家,都连到同一个在线场景服务器中玩。而如《DNF》这类游戏,社交场景所连接的“大厅服务器”本身也是可以按地图划分的。尽管游戏服务器架构的形态日趋复杂和分化,但其中的思想是统一的,就是“按业务逻辑所要求的数据缓存布局”来分布。


八、总结


游戏服务器和普通互联网业务服务器端,最大的区别实际上就在于“状态”。游戏服务器的状态是实时快速变化的、可以容忍丢失的、需要大量广播同步的;普通互联网业务服务器的状态一般是持久化的、不容忍丢失的、只和特定客户端相关的。

 

所以一个好的游戏服务器框架,在通讯和数据这两个基本层面,会和一般我们所接触的开源组件有很大的差异。所以现在大部分的游戏公司的服务器端,其实都不是使用完整的一个框架,甚至几个不同的项目,其服务器端架构都不一样。大家在看到阿里巴巴公司共享大量的开源软件的时候,也应该看到电子商务的业务特点,其实是比较适合做这种统一框架的。网易、腾讯、金山在游戏研发领域,其实也有很多的经验,但是很少能有一些开源软件公开出来。主要原因还是游戏领域的“通用性”太难把握。


Q&A


Q1:请问老师,现在有些游戏开始做全球同服,他们是怎么玩的?

A1:一般MMORPG是不会做全球同服,MOBA类和棋牌类是可以的。这一类架构,通过延迟较高的大厅服务器和分布全球不同机房的“房间服务器”来组合实现。现在欧美地区,在亚马逊云服务器上部署游戏,到各地的延迟还是不错的,所以也有一些游戏公司直接全部使用亚马逊。但是实际上,某些国家连上去还是很慢,比如我国、越南、俄罗斯。


Q2:能介绍下如何实时推送吗?

A2:实时推送在游戏里面,一般是TCP长连接下发数据,或者是UDP直接下发两种底层实现。游戏会自己实现从sendto()/recvfrom()到数据的编解码整套流程,所以这也是游戏框架难以统一的一个原因。游戏的代码风格,一般不会是请求-回应方式的,而是“收到某个类型的包”去处理的模式,这和“消息队列”的代码模式有点像。很多游戏的网络处理模块,包括服务器、客户端,都是一个大的switch...case...,里面按每个数据包的“包头协议ID”来做分发处理。而不是类似RPC那种风格的。


Q3:基于游戏的低延迟、状态、内存数据特性,很多工作都在进程内完成,运维、DBA工作有什么特别的地方,有什么痛点、难点吗?或者说从韩老师作为开发的角度,希望运维怎么样更好的支持?

A3:游戏的数据逻辑,大部分不依赖数据库,几乎没人在游戏领域用存储过程,这方面DBA可以松一口气,但是运维的部署工作就繁重很多。因为一套游戏里面,会分很多“区”和“服”,常常因为业务运营的需求,去做开新服开新区。这就代表着运维要不停地做新的服务器进程的部署。而且游戏里面的进程种类繁多,没有类似servlet容器这样的概念,每个进程都必须配置正确,整个游戏才会没有问题,而每个游戏的这套玩意都不一样。


除了开新服,合并老服务器也是大问题,因为存在两个数据库的记录要合并起来,设计不好就有很多索引冲突,逻辑上的故障,这其实才是DBA最常要解决的问题。举个例子,有一个很著名的MOBA游戏,他的运维部署工具是某个员工写的,后来这哥们离职了,就再也没有人能修改这个工具,也没人能理解这个工具,大家都只能跟着以前的做法去用。


好书相送

本文微信订阅号(dbaplus)评论区留下足以引起共鸣的真知灼见,并在本文发布后32小时之内成为点赞数最多的一名,可获得以下书籍一本~


特别鸣谢清华大学出版社为本次活动提供图书赞助。


精选专题(官网:dbaplus.cn)

◆  近期热文  ◆  

从0到15万,回望京东容器集群的3年建设之路

在Oracle专家眼中,MySQL sys Schema是怎样一种存在?

15年老司机的DPM数据库性能分析产品研发之路

DBA呢?我的数据库又双叒叕连不上了!

微店MySQL自动化运维体系的构建之路


◆  MVP专栏  ◆  

杨志洪杨建荣邹德裕韩锋欧阳辰

网易腾讯云百度朱祥磊卢钧轶

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存